package shadow

import (
	"errors"
	"sync"
	"time"
)

var instance DeviceShadow

var (
	UnknownDeviceErr = errors.New("unknown device")
	DeviceRepeatErr  = errors.New("device already exists")
)

const ON = true
const OFF = false

type DeviceShadow interface {
	AddDevice(name string, ttl time.Duration) error
	RemoveDevice(name string) error
	GetDevice(name string) (d Device, err error)
	GetStatus(name string) (online bool, err error)
	RefreshUpdateAt(name string) (err error)
	OnlineChangeCallback(func(name string, online bool))
	OnlineDevice() (sn []string)
	Destroy()
}

func init() {
	instance = newDeviceShadow()
}

func Of() DeviceShadow {
	return instance
}

func newDeviceShadow() DeviceShadow {
	shadow := &deviceShadow{
		m:      &sync.Map{},
		ticker: time.NewTicker(time.Second),
	}
	go shadow.statusCheck()
	return shadow
}

type deviceShadow struct {
	m             *sync.Map
	ticker        *time.Ticker
	statusHandler func(name string, online bool)
}

func (d *deviceShadow) AddDevice(name string, ttl time.Duration) error {
	if _, ok := d.m.Load(name); ok {
		return DeviceRepeatErr
	}
	device := NewDevice(name, ttl)
	d.m.Store(device.Name, device)
	return nil
}

func (d *deviceShadow) RemoveDevice(name string) error {
	if _, ok := d.m.Load(name); ok {
		d.m.Delete(name)
	}
	return nil
}

func (d *deviceShadow) GetDevice(name string) (device Device, err error) {
	if deviceAny, ok := d.m.Load(name); ok {
		return deviceAny.(Device), nil
	} else {
		return Device{}, UnknownDeviceErr
	}
}

func (d *deviceShadow) GetStatus(name string) (online bool, err error) {
	device, err := d.GetDevice(name)
	if err == nil {
		return device.online, nil
	}
	return false, err
}

func (d *deviceShadow) RefreshUpdateAt(name string) (err error) {
	device, err := d.GetDevice(name)
	if err == nil {
		if !device.online {
			device.online = true
			err := d.changeStatus(name, ON)
			if err != nil {
				return err
			}
		}
		device.updatedAt = time.Now()
		d.m.Store(name, device)
		return nil
	}
	return err
}

// OnlineDevice 获取在线的设备sn列表
func (d *deviceShadow) OnlineDevice() (sn []string) {
	var r []string
	d.m.Range(func(key, value any) bool {
		if d, ok := value.(Device); ok && d.online {
			r = append(r, d.Name)
		}
		return true
	})
	return r
}

func (d *deviceShadow) OnlineChangeCallback(callback func(name string, online bool)) {
	d.statusHandler = callback
}

func (d *deviceShadow) Destroy() {
	d.ticker.Stop()
}

func (d *deviceShadow) statusCheck() {
	for range d.ticker.C {
		d.m.Range(func(key, value any) bool {
			if device, ok := value.(Device); ok {
				if device.online && time.Now().Sub(device.updatedAt) > device.ttl {
					_ = d.changeStatus(device.Name, OFF)
				}
			}
			return true
		})
	}
}

func (d *deviceShadow) changeStatus(name string, online bool) error {
	if deviceAny, ok := d.m.Load(name); ok {
		device := deviceAny.(Device)
		if device.online != online {
			device.online = online
			device.updatedAt = time.Now()
			d.m.Store(name, device)
			if d.statusHandler != nil {
				d.statusHandler(name, online)
			}
		}
	} else {
		return UnknownDeviceErr
	}
	return nil
}
